const MagicString = require("magic-string");
const acorn = require("acorn");
const analyse = require("./util/analyse.js");
const GLOBALVARIABLE = require("./util/global.js");

module.exports = class Module {
    /**
     * @param {string} code 模块的代码字符串
     * @param {string} path 模块的绝对路径
     * @param {Bundle} bundle Bundle实例
     */
    constructor({ code = "", path = "", bundle } = {}) {
        this.code = new MagicString(code);
        this.path = path;
        this.bundle = bundle;
        this.ast = acorn.parse(this.code, {
            locations: true,
            ranges: true,
            sourceType: 'module',
            ecmaVersion: 7
        })

        /**
         * 当前模块导入的变量 ===> import { a as aaa, b } from "./filePath.js";
         * {
         *      a: { name: "a", localName: "aaa", source: "./filePath.js" },
         *      b: { name: "b", localName: "b", source: "./filePath.js" },
         * }
         */
        this.imports = {};
        /**
         * 当前模块导出的变量 ===> export const name = "anyu", age = 18;
         * {
         *      name: { node: node(导出export)节点, localName: "name", expression: node(变量name)节点 },
         *      name: { node: node(导出export)节点, localName: "age", expression: node(变量age)节点 },
         * }
         */
        this.exports = {};
        /**
         * 当前模块所有全局变量的定义语句:
         * const a = "a";
         * const fn = () => "fn";
         * const b = true;
         *
         * {
         *      a: node a的节点
         *      fn: node fn的节点
         *      b: node b的节点
         * }
         */
        this.definitions = {};

        this.analyse();
    }

    /**
     * 分析模块的ast语法树
     */
    analyse() {
        // 1. 解析ast语法树中的导入和导出
        this.ast.body.forEach(node => {
            // 判断当前语句的类型
            switch(node.type) {
                // 是导入语句 import { a as aaa, b } from "./filePath.js";
                case "ImportDeclaration":
                    const specifiers = node.specifiers; // 导入变量的数组
                    const importFilePath = node.source.value; // 导入语句中的文件路径 ===> ./filePath.js
                    specifiers.forEach(specifier => {
                        const name = specifier.imported.name; //    a    b
                        const localName = specifier.local.name; //  aaa    b
                        this.imports[localName] = {
                            name,
                            localName,
                            source: importFilePath
                        }
                    })
                    break;
                // 是导出语句 export const name = "anyu", age = 18;
                case "ExportNamedDeclaration":
                    if(!node.declaration.declarations) return;
                    const name = node.declaration.declarations[0].id.name;
                    this.exports[name] = {
                        localName: name,
                        node,
                        expression: node.declaration
                    }
                    break;
                default:
                    break;
            }
        })

        // 2. 解析ast语法树的
        //    得到模块的全局变量_defines；
        //    模块中用到的从外部导入的变量 或 函数作用域外的变量_dependsOn
        analyse(this.ast, this.code);

        // 3. 解析ast语法树中的全局变量声明
        this.ast.body.forEach(node => {
            Object.keys(node._defines).forEach(name => {
                this.definitions[name] = node;
            })
            // console.log("_dependsOn", node._dependsOn);
            // console.log("_defines", node._defines);
        })
        // console.log(this.imports);
        // console.log(this.exports);
        // console.log("definitions", this.definitions);
    }

    /**
     * 组装方法，遍历ast树，找到调用过的函数和变量
     */
    expandAllStatements() {
        const allStatements = [];

        this.ast.body.forEach(node => {
            // 当前节点是函数调用
            if(node.type === "ExpressionStatement") {
                // 留下函数调用，所用到的变量和函数
                const statements = this.expandStatement(node);

                allStatements.push(...statements);
            }
        })

        return allStatements;
    }

    /**
     * 找到当前节点中用到的从外部导入的变量
     * @param {object} node 模块最外层的node节点
     */
    expandStatement(node) {
        node._included = true; // 当前语句已经被处理
        const result = []; // 存放node节点[node, node, ......]

        // 1. 获取模块中用到的从外部导入的变量
        const _dependsOn = Object.keys(node._dependsOn);

        // 2. 将外部导入的变量的node节点存放到result中
        _dependsOn.forEach(keyName => {
            const defines = this.define(keyName)

            result.push(...defines);
        })

        // 3. 添加自己
        result.push(node);

        return result;
    }

    /**
     * 查找变量声明
     * @param {string} name 当前node中用到的从外部导入的变量
     */
    define(name) {
        // 判断当前节点用到的外部变量，在外部导入变量中是否存在
        if(ownProperty(this.imports, name)) {
            // 变量是外部导入的

            // 1. 获取到这个变量的信息
            const importData = this.imports[name];
            // 2. 得到导入这个变量的路径
            const source = importData.source;
            // 3. 加载此模块
            const newModule = this.bundle.fetchModule(source, this.path);
            // 4. 得到这个新模块的导出信息
            const exportData = newModule.exports[name];

            return exportData ? newModule.define(exportData.localName) : []
        } else {
            // 变量来自当前模块
            const statement = this.definitions[name];

            // 变量是全局变量
            if(statement) {
                if(statement._included) {
                    // 如果已经收集过，则不再重复收集
                    return [];
                } else {
                    // 递归    const a = b + 1;
                    return this.expandStatement(statement);
                }
            }
            // 当前的函数是否是全局的console / window 等
            else if(GLOBALVARIABLE.includes(name)) {
                return [];
            }
            // 当前模块没有此变量，报错
            else {
                throw new Error(`没有此变量: ${name}`);
            }
        }
    }
}

// 判断对象上是否有某个属性
const ownProperty = (object, prop ) => Object.prototype.hasOwnProperty.call(object, prop);